{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# OpenAPI.NET Notebook Sample\n",
    "\n",
    "In this sample we will use OpenAPI.NET package to access cTrader Open API, and show a trading account real time stats.\n",
    "\n",
    "Let's start by installing the Nuget package:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "#r \"nuget: Spotware.OpenAPI.Net\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we will add the OpenAPI.NET usings:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "using OpenAPI.Net;\n",
    "using OpenAPI.Net.Auth;\n",
    "using OpenAPI.Net.Helpers;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "source": [
    "Please create a credentials.json file in current directory of notebook file and fill it with your Open API credentials:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "source": [
    "```json\n",
    "{\n",
    "  \"ClientId\": \"your app client id\",\n",
    "  \"Secret\": \"your app secret\",\n",
    "  \"AccessToken\": \"your access token, create one by using your app playground on open API site\",\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will load your created credentials file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "using System.Text.Json;\n",
    "using System.Text.Json.Serialization;\n",
    "using System.IO;\n",
    "\n",
    "public record Credentials(string ClientId, string Secret, string AccessToken);\n",
    "\n",
    "private async Task<Credentials> GetCredentials()\n",
    "{\n",
    "    using var stream = File.OpenRead(\"credentials.json\");\n",
    "    return  await JsonSerializer.DeserializeAsync<Credentials>(stream);\n",
    "}\n",
    "\n",
    "var credentials = await GetCredentials();\n",
    "\n",
    "Console.WriteLine(credentials);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We got the API credentials, now lets create a method for getting OpenAPI.NET client, this client will first autorize our API application and then trading account:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "using System.Reactive.Linq;\n",
    "using Google.Protobuf;\n",
    "\n",
    "private async Task<OpenClient> GetClient(Mode mode)\n",
    "{\n",
    "    var host = ApiInfo.GetHost(mode);\n",
    "\n",
    "    var client = new OpenClient(host, ApiInfo.Port, TimeSpan.FromSeconds(15));\n",
    "\n",
    "    client.Subscribe(_ => {}, exception => Console.WriteLine($\"Exception: {exception}\"));\n",
    "    client.OfType<ProtoErrorRes>().Subscribe(error => Console.WriteLine($\"Exception: {error}\"));\n",
    "\n",
    "    bool isAppAuthroized = false;\n",
    "\n",
    "    void OnAppAuthorized(ProtoOAApplicationAuthRes response) => isAppAuthroized = true;\n",
    "    \n",
    "    client.OfType<ProtoOAApplicationAuthRes>().Subscribe(OnAppAuthorized);\n",
    "    \n",
    "    await client.Connect();\n",
    "    \n",
    "    var applicationAuthReq = new ProtoOAApplicationAuthReq\n",
    "    {\n",
    "        ClientId = credentials.ClientId,\n",
    "        ClientSecret = credentials.Secret,\n",
    "    };\n",
    "    \n",
    "    await client.SendMessage(applicationAuthReq, applicationAuthReq.PayloadType);\n",
    "\n",
    "    var waitStartTime = DateTime.Now;\n",
    "\n",
    "    while (isAppAuthroized is false && DateTime.Now - waitStartTime < TimeSpan.FromSeconds(2))\n",
    "    {\n",
    "        await Task.Delay(100);\n",
    "    }\n",
    "\n",
    "    if (isAppAuthroized is false) throw new InvalidOperationException(\"Account authorization failed\");\n",
    "\n",
    "    return client;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "source": [
    "After create a method to an API client we need to get all the authorized accounts for your access token:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "// Please set one of the authorized trading accounts number\n",
    "// We will use it for future requests\n",
    "const long AccountNumber = 3397885;\n",
    "\n",
    "if (AccountNumber == 0) throw new InvalidOperationException(\"Please first set the account number\");\n",
    "\n",
    "private async Task<ProtoOAGetAccountListByAccessTokenRes> GetAccounts()\n",
    "{\n",
    "    var request = new ProtoOAGetAccountListByAccessTokenReq()\n",
    "    {\n",
    "        AccessToken = credentials.AccessToken\n",
    "    };\n",
    "\n",
    "    ProtoOAGetAccountListByAccessTokenRes result = null;\n",
    "\n",
    "    void OnResponse(ProtoOAGetAccountListByAccessTokenRes response) => result = response;\n",
    "\n",
    "    using var client = await GetClient(Mode.Live);\n",
    "\n",
    "    client.OfType<ProtoOAGetAccountListByAccessTokenRes>().Subscribe(OnResponse);\n",
    "\n",
    "    await client.SendMessage(request, request.PayloadType);\n",
    "\n",
    "    await Task.Delay(1000);\n",
    "\n",
    "    if (result is null) throw new InvalidOperationException($\"Couldn't get response\");\n",
    "\n",
    "    return result;\n",
    "}\n",
    "\n",
    "var accounts = await GetAccounts();\n",
    "\n",
    "var account = accounts.CtidTraderAccount.First(account => account.TraderLogin == AccountNumber);\n",
    "\n",
    "Console.WriteLine($\"Account ID is: {account.CtidTraderAccountId}\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To use an account you have to first authroize it, otherwise you will not be able to send any request related to that account, let's create method for authorized an account: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "private async Task<ProtoOAAccountAuthRes> AuthorizeAccount(OpenClient client, long accountId)\n",
    "{\n",
    "    var request = new ProtoOAAccountAuthReq()\n",
    "    {\n",
    "        AccessToken = credentials.AccessToken,\n",
    "        CtidTraderAccountId = accountId\n",
    "    };\n",
    "\n",
    "    ProtoOAAccountAuthRes result = null;\n",
    "\n",
    "    void OnResponse(ProtoOAAccountAuthRes response) => result = response;\n",
    "    \n",
    "    client.OfType<ProtoOAAccountAuthRes>().Subscribe(OnResponse);\n",
    "\n",
    "    await client.SendMessage(request, request.PayloadType);\n",
    "\n",
    "    await Task.Delay(500);\n",
    "\n",
    "    if (result is null) throw new InvalidOperationException($\"Couldn't get response\");\n",
    "\n",
    "    return result;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "source": [
    "We will use the AuthorizeAccount whenever we created a new client after application authorization."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now our account and API application is authorized and we can start interacting with the API, let's first create a method for getting a symbol data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "// Be sure your trading account has a symbol with this name,\n",
    "// if not change it to another symbol name\n",
    "const string SymbolName = \"EURUSD\";\n",
    "\n",
    "private async Task<ProtoOALightSymbol> GetSymbol(string symbolName)\n",
    "{\n",
    "    var request = new ProtoOASymbolsListReq\n",
    "    {\n",
    "        CtidTraderAccountId = (long)account.CtidTraderAccountId,\n",
    "    };\n",
    "\n",
    "    ProtoOALightSymbol result = null;\n",
    "\n",
    "    void OnResponse(ProtoOASymbolsListRes response)\n",
    "    {\n",
    "        result = response.Symbol.FirstOrDefault(symbol => symbol.SymbolName.Equals(SymbolName, StringComparison.OrdinalIgnoreCase));\n",
    "    }\n",
    "\n",
    "    using var client = await GetClient(account.IsLive ? Mode.Live : Mode.Demo);\n",
    "\n",
    "    await AuthorizeAccount(client, accountId);\n",
    "\n",
    "    client.OfType<ProtoOASymbolsListRes>().Subscribe(OnResponse);\n",
    "\n",
    "    await client.SendMessage(request, request.PayloadType);\n",
    "\n",
    "    await Task.Delay(1000);\n",
    "\n",
    "    if (result is null) throw new InvalidOperationException($\"Couldn't get/find the symbol {SymbolName}\");\n",
    "\n",
    "    return result;\n",
    "}\n",
    "\n",
    "var symbol = await GetSymbol(SymbolName);\n",
    "Console.WriteLine(symbol);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next we will get the above symbol daily trend bars or OHLC price data data from API:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "private async Task<ProtoOAGetTrendbarsRes> GetTrendbars(long symbolId, DateTimeOffset from, DateTimeOffset to)\n",
    "{\n",
    "    var request = new ProtoOAGetTrendbarsReq()\n",
    "    {\n",
    "        Period = ProtoOATrendbarPeriod.D1,\n",
    "        CtidTraderAccountId = (long)account.CtidTraderAccountId,\n",
    "        SymbolId = symbolId,\n",
    "        FromTimestamp = from.ToUnixTimeMilliseconds(),\n",
    "        ToTimestamp = to.ToUnixTimeMilliseconds()\n",
    "    };\n",
    "\n",
    "    ProtoOAGetTrendbarsRes result = null;\n",
    "\n",
    "    void OnResponse(ProtoOAGetTrendbarsRes response) => result = response;\n",
    "\n",
    "    using var client = await GetClient(account.IsLive ? Mode.Live : Mode.Demo);\n",
    "\n",
    "    await AuthorizeAccount(client, accountId);\n",
    "\n",
    "    client.OfType<ProtoOAGetTrendbarsRes>().Subscribe(OnResponse);\n",
    "\n",
    "    await client.SendMessage(request, request.PayloadType);\n",
    "\n",
    "    await Task.Delay(1000);\n",
    "\n",
    "    if (result is null) throw new InvalidOperationException($\"Couldn't get response\");\n",
    "\n",
    "    return result;\n",
    "}\n",
    "\n",
    "// We get one year of daily bars\n",
    "var trendbars = await GetTrendbars(symbol.SymbolId, DateTimeOffset.UtcNow.AddYears(-1), DateTimeOffset.UtcNow);\n",
    "\n",
    "Console.WriteLine(trendbars)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What about getting account open positions and orders? let's do it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "private async Task<ProtoOAReconcileRes> GetOrdersAndPositions()\n",
    "{\n",
    "    var request = new ProtoOAReconcileReq()\n",
    "    {\n",
    "        CtidTraderAccountId = (long)account.CtidTraderAccountId,\n",
    "    };\n",
    "\n",
    "    ProtoOAReconcileRes result = null;\n",
    "\n",
    "    void OnResponse(ProtoOAReconcileRes response) => result = response;\n",
    "\n",
    "    using var client = await GetClient(account.IsLive ? Mode.Live : Mode.Demo);\n",
    "\n",
    "    await AuthorizeAccount(client, accountId);\n",
    "\n",
    "    client.OfType<ProtoOAReconcileRes>().Subscribe(OnResponse);\n",
    "\n",
    "    await client.SendMessage(request, request.PayloadType);\n",
    "\n",
    "    await Task.Delay(1000);\n",
    "\n",
    "    if (result is null) throw new InvalidOperationException($\"Couldn't get response\");\n",
    "\n",
    "    return result;\n",
    "}\n",
    "\n",
    "var ordersAndPositions = await GetOrdersAndPositions();\n",
    "\n",
    "Console.WriteLine(ordersAndPositions)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Please don't use ProtoOAReconcileReq every time you need account open orders/positions, instead use the ExecutionEvent and use ProtoOAReconcileReq only once for getting the already open orders/positions."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All of the above methods return data from account, but they don't perform any trading action, let's do some trading, we will create a market order, a limit order, and a stop order:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "outputs": [],
   "source": [
    "public async Task CreateNewOrder(ProtoOAOrderType orderType, long volume, ProtoOATradeSide tradeSide, double limitPrice = 0, double stopPrice = 0)\n",
    "{\n",
    "    var request = new ProtoOANewOrderReq\n",
    "    {\n",
    "        CtidTraderAccountId = (long)account.CtidTraderAccountId,\n",
    "        SymbolId = symbol.SymbolId,\n",
    "        Volume = volume,\n",
    "        TradeSide = tradeSide,\n",
    "        OrderType = orderType\n",
    "    };\n",
    "\n",
    "    if (request.OrderType == ProtoOAOrderType.Limit)\n",
    "    {\n",
    "        request.LimitPrice = limitPrice;\n",
    "    }\n",
    "\n",
    "    if (request.OrderType == ProtoOAOrderType.Stop)\n",
    "    {\n",
    "        request.StopPrice = stopPrice;\n",
    "    }\n",
    "\n",
    "    using var client = await GetClient(account.IsLive ? Mode.Live : Mode.Demo);\n",
    "\n",
    "    await AuthorizeAccount(client, accountId);\n",
    "\n",
    "    await client.SendMessage(request, request.PayloadType);\n",
    "\n",
    "    await Task.Delay(1000);\n",
    "}\n",
    "\n",
    "// First market order, change the volume if it doesn't match symbol min volume\n",
    "// Volume is in units * 100, so 1000 units equal 100000\n",
    "await CreateNewOrder(ProtoOAOrderType.Market, 100000, ProtoOATradeSide.Buy);\n",
    "// Limit order, change the price based on current symbol price\n",
    "await CreateNewOrder(ProtoOAOrderType.Limit, 100000, ProtoOATradeSide.Buy, limitPrice: 1.5);\n",
    "// Stop order, change the price based on current symbol price\n",
    "await CreateNewOrder(ProtoOAOrderType.Stop, 100000, ProtoOATradeSide.Buy, stopPrice: 1.5);\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    }
   },
   "source": [
    "Please check the ProtoOANewOrderReq documentation for setting other order properites like stop loss, take profit, stop limit orders, market range orders, etc."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This was our .NET notebook sample, please check our other samples for more future rich apps like our Blazor web assembly sample.\n",
    "\n",
    "We love to hear from you and see you contribute to OpenAPI.NET repository."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "name": "C#"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
